/* *****************************************************************************
 * JFab (http://code.google.com/p/jfab)
 * Copyright (c) 2011 JFab.org
 * Admin jfab@jeffreythompson.net
 *
 * See the file "LICENSE.txt" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * *****************************************************************************/

package org.jfab.core.io;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;

import org.apache.commons.lang3.StringUtils;
import org.jfab.core.analyzer.ClassAnalyzer;
import org.jfab.core.analyzer.EnumAnalyzer;
import org.jfab.core.analyzer.InterfaceAnalyzer;
import org.jfab.core.model.CGCompilationUnit;
import org.jfab.core.util.Compiler;
import org.jfab.core.util.JavaStringCompilerException;

/**
 * Provides a compilation unit reader.
 */
public final class CGCompilationUnitReader implements
        CGReader<CGCompilationUnit>
{
    /** Class analyzer. */
    private final ClassAnalyzer _classAnalyzer;

    /** Compiler. */
    private final Compiler<?> _compiler;

    /** Enum analyzer. */
    private final EnumAnalyzer _enumAnalyzer;

    /** Interface analyzer. */
    private final InterfaceAnalyzer _interfaceAnalyzer;

    /**
     * Construct this object with the given parameters.
     * 
     * @param interfaceAnalyzer Interface analyzer.
     * @param classAnalyzer Class analyzer.
     * @param enumAnalyzer Enum analyzer.
     * @param compiler Compiler.
     */
    public CGCompilationUnitReader(final InterfaceAnalyzer interfaceAnalyzer,
            final ClassAnalyzer classAnalyzer, final EnumAnalyzer enumAnalyzer,
            final Compiler<?> compiler)
    {
        _interfaceAnalyzer = interfaceAnalyzer;
        _classAnalyzer = classAnalyzer;
        _enumAnalyzer = enumAnalyzer;
        _compiler = compiler;
    }

    /**
     * @see org.jfab.core.io.CGReader#read(java.io.Reader)
     */
    @Override
    public final CGCompilationUnit read(final Reader reader)
    {
        CGCompilationUnit answer = null;

        try
        {
            // Read file contents.
            final StringBuilder sb = new StringBuilder();
            final BufferedReader bufReader = new BufferedReader(reader);
            String line = null;

            while ((line = bufReader.readLine()) != null)
            {
                sb.append(line).append("\n");
            }

            final String javaSource = sb.toString();

            // Compile.
            final String className = parseClassName(javaSource);
            final Class<?> aClass = compileClass(className, javaSource);

            // Create.
            if (aClass.isInterface())
            {
                answer = getInterfaceAnalyzer().analyze(aClass, javaSource);
            }
            else if (aClass.isEnum())
            {
                answer = getEnumAnalyzer().analyze(aClass, javaSource);
            }
            else
            {
                answer = getClassAnalyzer().analyze(aClass, javaSource);
            }
        }
        catch (final IOException e)
        {
            throw new JavaIOReaderException(e);
        }

        return answer;
    }

    /**
     * @param className Class name.
     * @param javaSource Java source code.
     * 
     * @return a new code generator class.
     */
    private Class<?> compileClass(final String className,
            final String javaSource)
    {
        Class<?> aClass = null;

        try
        {
            aClass = Class.forName(className);
        }
        catch (final ClassNotFoundException e)
        {
            final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
            final Class<?> types = Object.class;

            try
            {
                aClass = getCompiler().compile(className, javaSource,
                        diagnostics, types);
            }
            catch (final JavaStringCompilerException e1)
            {
                printDiagnosticsReport(diagnostics);
                throw e1;
            }
        }

        return aClass;
    }

    /**
     * @return the classAnalyzer
     */
    private ClassAnalyzer getClassAnalyzer()
    {
        return _classAnalyzer;
    }

    /**
     * @return the compiler
     */
    private Compiler<?> getCompiler()
    {
        return _compiler;
    }

    /**
     * @return the enumAnalyzer
     */
    private EnumAnalyzer getEnumAnalyzer()
    {
        return _enumAnalyzer;
    }

    /**
     * @return the interfaceAnalyzer
     */
    private InterfaceAnalyzer getInterfaceAnalyzer()
    {
        return _interfaceAnalyzer;
    }

    /**
     * @param javaSource Java source.
     * 
     * @return the fully qualified class name.
     */
    private String parseClassName(final String javaSource)
    {
        String answer = null;

        if (StringUtils.isNotEmpty(javaSource))
        {
            final String packageName = parsePackageName(javaSource);
            final String shortClassName = parseShortClassName(javaSource);

            answer = packageName + "." + shortClassName;
        }

        return answer;
    }

    /**
     * @param javaSource Java source.
     * 
     * @return the package name.
     */
    private String parsePackageName(final String javaSource)
    {
        String answer = null;

        if (StringUtils.isNotEmpty(javaSource))
        {
            final String line = StringUtils.substringBetween(javaSource,
                    "package ", ";");
            answer = line;
        }

        return answer;
    }

    /**
     * @param javaSourceIn Java source.
     * 
     * @return the short class name.
     */
    private String parseShortClassName(final String javaSourceIn)
    {
        String answer = null;

        if (StringUtils.isNotEmpty(javaSourceIn))
        {
            int index = javaSourceIn.indexOf('{');
            String javaSource = javaSourceIn.substring(0, index + 1);
            final String key0 = "*/";
            index = javaSource.indexOf("*/");

            if (index >= 0)
            {
                javaSource = javaSource.substring(index + key0.length());
            }

            javaSource = javaSource.replaceAll("\n", "");

            String line = StringUtils.substringBetween(javaSource, "class ",
                    " ");

            if (StringUtils.isEmpty(line))
            {
                line = StringUtils.substringBetween(javaSource, "class ", "{");

                if (StringUtils.isEmpty(line))
                {
                    line = StringUtils.substringBetween(javaSource,
                            "interface ", " ");

                    if (StringUtils.isEmpty(line))
                    {
                        line = StringUtils.substringBetween(javaSource,
                                "interface ", "{");

                        if (StringUtils.isEmpty(line))
                        {
                            line = StringUtils.substringBetween(javaSource,
                                    "enum ", "{");

                            if (StringUtils.isEmpty(line))
                            {
                                line = StringUtils.substringBetween(javaSource,
                                        "enum ", " ");
                            }
                        }
                    }
                }
            }

            if (line != null)
            {
                line = line.trim();
                line = StringUtils.chomp(line, "{");
                answer = removeTypeParameter(line);
            }
        }

        return answer;
    }

    /**
     * Print a diagnostics report.
     * 
     * @param diagnostics Diagnostics.
     */
    private void printDiagnosticsReport(
            final DiagnosticCollector<JavaFileObject> diagnostics)
    {
        System.out.println("\nDiagnostics Report\n");

        for (final Diagnostic<?> diagnostic : diagnostics.getDiagnostics())
        {
            System.out.println("diagnostic = " + diagnostic);
        }
    }

    /**
     * @param line Line.
     * 
     * @return the modified string.
     */
    private String removeTypeParameter(final String line)
    {
        String answer = line;
        final int index0 = line.indexOf("<");

        if (index0 >= 0)
        {
            final int index1 = line.indexOf(">", index0);

            if (index1 >= 0)
            {
                answer = line.substring(0, index0) + line.substring(index1 + 1);
            }
            else
            {
                answer = line.substring(0, index0);
            }
        }

        return answer;
    }
}
